package io.xxx.omni.sync.impl

import com.alibaba.fastjson.JSONObject
import com.google.common.util.concurrent.RateLimiter
import io.xxx.omni.sync.*
import io.xxx.omni.util.offset
import io.xxx.omni.util.toLocalDateTime
import org.apache.commons.codec.digest.DigestUtils
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import org.springframework.util.StringUtils
import org.springframework.web.util.UriComponentsBuilder
import java.security.MessageDigest
import java.time.Duration
import java.time.LocalDateTime
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.collections.HashMap

/**
 * 腾讯惠聚
 */
@Suppress("UnstableApiUsage")
abstract class TxHjPorter : PagePorter() {

    private val httpUrl = "https://qy.sr.qq.com/openapi/v1"

    override val platformCode = "tengxunhuiju"

    protected abstract val path: String

    private val limiter = RateLimiter.create(5.0)

    override fun acquire() {
        limiter.acquire()
    }

    override val pageSize = 50

    override val duration: Duration = Duration.ofMinutes(30)

    override fun getStartTime(storeJob: StoreJob): LocalDateTime {
        val minStartTime = LocalDateTime.now().minusMonths(3).plusMinutes(1)
        return if (storeJob.endTime!! < minStartTime) minStartTime else storeJob.endTime!!
    }

    protected fun getResponse(
        store: Store,
        path: String,
        params: Map<String, String> = emptyMap()
    ): JSONObject {
        val appKey = store.appKey!!
        val appSecret = store.appSecret!!
        val signMap = TreeMap(params)
        val now = System.currentTimeMillis().toString()
        signMap["requestId"] = UUID.randomUUID().toString().replace("-".toRegex(), "")
        signMap["appId"] = appKey
        signMap["timestamp"] = now
        signMap["nonceStr"] = DigestUtils.md5Hex(now).substring(4, 10)
        val builder = StringBuilder()
        signMap.forEach { (k, v) ->
            builder.append(k).append("=").append(v).append("&")
        }
        builder.append("appSecret").append("=").append(appSecret)
        val sign = DigestUtils.md5Hex(builder.toString()).toUpperCase()
        val url = UriComponentsBuilder.fromHttpUrl(httpUrl)
            .path(path)
            .toUriString()
        val map = HashMap(signMap)
        map["sign"] = sign
        val response = restTemplate.postForEntity(url, map, JSONObject::class.java)
        if (response.statusCode.isError || response.body == null) {
            val msg = "接口[url: $url, body: $map]异常"
            throw RuntimeException(msg)
        }
        val body = response.body!!
        val code = body.getIntValue("code")
        if (code != 0) {
            val msg = "接口[url: $url, body: $map]异常: $body"
            log.error(msg)
            throwException(url, body.getString("code"), body.getString("message"))
        }
        return body.getJSONObject("data")
    }

    companion object {
        private val log = LoggerFactory.getLogger(TxHjPorter::class.java)
    }
}

@Component
class TxHjTradePorter : TxHjPorter() {

    override val path = "/order/list/increment"

    override val jobId = 1

    override fun getCount(store: Store, startTime: LocalDateTime, endTime: LocalDateTime, parameter: Any?): Long? {
        val params = getParams(startTime, endTime)
        val response = getResponse(store, path, params)
        return response.getLong("totalCount")
    }

    override fun getData(
        store: Store,
        startTime: LocalDateTime,
        endTime: LocalDateTime,
        parameter: Any?,
        pageNo: Long,
    ): List<Document>? {
        val params = getParams(startTime, endTime)
        val response = getResponse(store, path, params)
        val dataKey = store.accessToken!!
        return response.getJSONArray("orderList")
            .map {
                @Suppress("unchecked_cast")
                val value = JSONObject(it as Map<String, Any>)
                value["userName"] = decrypt(value.getString("userName"), dataKey)
                value["telNumber"] = decrypt(value.getString("telNumber"), dataKey)
                value["provinceName"] = decrypt(value.getString("provinceName"), dataKey)
                value["cityName"] = decrypt(value.getString("cityName"), dataKey)
                value["countyName"] = decrypt(value.getString("countyName"), dataKey)
                value["detailInfo"] = decrypt(value.getString("detailInfo"), dataKey)
                value["deliverNumber"] = decrypt(value.getString("deliverNumber"), dataKey)
                value["deliverCompany"] = decrypt(value.getString("deliverCompany"), dataKey)
                val sn = value.getString("orderId")
                Document(sn, value, value.getLong("updateTime").toLocalDateTime())
            }
    }

    private fun getParams(
        startTime: LocalDateTime,
        endTime: LocalDateTime,
        pageNo: Long = 1
    ): Map<String, String> {
        return mapOf(
            "startUpdateTime" to startTime.toEpochSecond(offset).toString(),
            "endUpdateTime" to endTime.toEpochSecond(offset).toString(),
            "pageNum" to pageNo.toString(),
            "pageSize" to pageSize.toString(),
        )
    }

    private fun decrypt(encryptedData: String, dataKey: String): String? {
        return if (!StringUtils.hasText(encryptedData)) {
            null
        } else decrypt(Base64.getDecoder().decode(encryptedData), dataKey)
    }

    private fun decrypt(encryptedIvTextBytes: ByteArray, key: String): String {
        val ivSize = 16
        val keySize = 16
        val iv = ByteArray(ivSize)
        System.arraycopy(encryptedIvTextBytes, 0, iv, 0, iv.size)
        val ivParameterSpec = IvParameterSpec(iv)
        val encryptedSize = encryptedIvTextBytes.size - ivSize
        val encryptedBytes = ByteArray(encryptedSize)
        System.arraycopy(encryptedIvTextBytes, ivSize, encryptedBytes, 0, encryptedSize)
        val keyBytes = ByteArray(keySize)
        val md = MessageDigest.getInstance("SHA-256")
        md.update(key.toByteArray())
        System.arraycopy(md.digest(), 0, keyBytes, 0, keyBytes.size)
        val secretKeySpec = SecretKeySpec(keyBytes, "AES")
        val cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding")
        cipherDecrypt.init(2, secretKeySpec, ivParameterSpec)
        val decrypted = cipherDecrypt.doFinal(encryptedBytes)
        return String(decrypted)
    }
}

@Component
class TxHjRefundPorter : TxHjPorter() {

    override val path = "/aftersale/list/increment"

    override val jobId = 2

    override fun getCount(store: Store, startTime: LocalDateTime, endTime: LocalDateTime, parameter: Any?): Long? {
        val params = getParams(startTime, endTime)
        val response = getResponse(store, path, params)
        return response.getLong("totalCount")
    }

    override fun getData(
        store: Store,
        startTime: LocalDateTime,
        endTime: LocalDateTime,
        parameter: Any?,
        pageNo: Long,
    ): List<Document>? {
        val params = getParams(startTime, endTime)
        val response = getResponse(store, path, params)
        return response.getJSONArray("list")
            .map {
                @Suppress("unchecked_cast")
                val value = JSONObject(it as Map<String, Any>)
                val sn = value.getString("afterSaleOrderId")
                val rsn = value.getString("orderId")
                Document(sn, rsn, value, value.getLong("updateTime").toLocalDateTime())
            }
    }

    private fun getParams(
        startTime: LocalDateTime,
        endTime: LocalDateTime,
        pageNo: Long = 1
    ): Map<String, String> {
        return mapOf(
            "startUpdateTime" to startTime.toEpochSecond(offset).toString(),
            "endUpdateTime" to endTime.toEpochSecond(offset).toString(),
            "pageNum" to pageNo.toString(),
            "pageSize" to pageSize.toString(),
        )
    }
}